package com.couduidui.my.app.aop;

import java.util.Arrays;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * Spring boot aop 练习 入口程序必须放到最外层 否则入口程序的上层代码spring扫描不到
 * http://www.cnblogs.com/jun-ma/p/4844978.html  自定义注解的方式成为切点   有空研究
 * 
 * @author 错对对
 * @date 2017年6月20日 下午5:50:35
 */
@Aspect
@Component
public class WebLogAspect {

	private Logger logger = Logger.getLogger(getClass());

	/**
	 * 匹配com.couduidui.my.app.*.controller包及其子包下的所有类的所有方法
	 * 
	 * @author 错对对
	 * @date 2017年6月21日
	 */
	@Pointcut("execution(* com.couduidui.my.app.*.controller ..*.*(..))")
	public void webLog() {
	}

	/**
	 * 前置通知，方法调用前被调用
	 * 
	 * @param joinPoint
	 * @throws Throwable
	 * @author 错对对
	 * @date 2017年6月21日
	 */
	@Before("webLog()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		// 接收到请求，记录请求内容
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		// 记录下请求内容
		logger.info("URL : " + request.getRequestURL().toString());
		logger.info("HTTP_METHOD : " + request.getMethod());
		logger.info("IP : " + request.getRemoteAddr());
		logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
				+ joinPoint.getSignature().getName());
		logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));

	}

	/**
	 * 后置返回通知 这里需要注意的是: 如果参数中的第一个参数为JoinPoint，则第二个参数为返回值的信息
	 * 如果参数中的第一个参数不为JoinPoint，则第一个参数为returning中对应的参数
	 * 
	 * @param ret
	 * @throws Throwable
	 * @author 错对对
	 * @date 2017年6月21日
	 */
	@AfterReturning(returning = "ret", pointcut = "webLog()")
	public void doAfterReturning(Object ret) throws Throwable {
		// 处理完请求，返回内容
		logger.info("RESPONSE : " + ret);
	}
	//
	// @AfterReturning(value = "execution(execution(*
	// com.couduidui.my.app.*.controller ..*.*(..))",returning = "keys")
	// public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){
	//
	// System.out.println("第一个后置返回通知的返回值："+keys);
	// }

	/**
	 * 后置异常通知 定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
	 * throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
	 * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
	 * 
	 * @param joinPoint
	 * @param exception
	 */
	@AfterThrowing(value = "webLog()", throwing = "exception")
	public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
		// 目标方法名：
		System.out.println(joinPoint.getSignature().getName());
		if (exception instanceof NullPointerException) {
			System.out.println("发生了空指针异常!!!!!");
		}
	}

	/**
	 * 后置最终通知（目标方法只要执行完了就会执行后置通知方法）
	 * 
	 * @param joinPoint
	 */
	@After("webLog()")
	public void doAfterAdvice(JoinPoint joinPoint) {

		System.out.println("后置通知执行了!!!!");
	}

	/**
	 * 环绕通知： 环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
	 * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
	 */
	@Around("webLog()")
	public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
		System.out.println("环绕通知的目标方法名：" + proceedingJoinPoint.getSignature().getName());
		try {
			// obj之前可以写目标方法执行前的逻辑
			// 调用执行目标方法
			Object obj = proceedingJoinPoint.proceed();
			return obj;
		} catch (Throwable throwable) {
			throwable.printStackTrace();
		}
		return null;
	}
}